﻿(*                  FnSharp              

   Base class library extensions and frameworks aimed 
   specifically at improving F# developers lives!

   Written by: Huw Simpson
   License: Microsoft Public License (Ms-PL) 
   Available at: http://fnsharp.codeplex.com

*)
namespace FnSharp.Bcl.Core

    open System
    open System.Collections.Generic
    
    type YieldGroupItemAction =
        | GroupItem
        | AppendItem
        | DiscardItem
        | SeperateItem

    type ContinueGroupItemAction =
        | WithItem
        | WithoutItem

    type GroupingAction =
        | YieldGroup of YieldGroupItemAction
        | ContinueGroup of ContinueGroupItemAction

    [<RequireQualifiedAccess>]
    [<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
    module Array =
        /// Group the elements in an array by a supplied function which returns the desired grouping action.
        [<CompiledName("GroupBy2")>]
        let groupBy2 f (source:'t array) = [|
            let group = new List<'t> ()
            let drain () = group.Count > 0 |> Option.ofBoolF2 (group.ToArray) (ignore >> group.Clear)

            for e in source do
                match f e (group.ToArray ()) with
                | YieldGroup itemAction ->
                    match drain (), itemAction with
                    | None,     GroupItem    -> group.Add e
                    | Some g,   GroupItem    -> group.Add e; yield g
                    | None,     AppendItem   -> yield [|e|]
                    | Some g,   AppendItem   -> yield [|e|] |> Array.append g
                    | None,     DiscardItem  -> ()
                    | Some g,   DiscardItem  -> yield g
                    | None,     SeperateItem -> yield [|e|]
                    | Some g,   SeperateItem -> yield g; yield [|e|]
                | ContinueGroup itemAction ->
                    match itemAction with
                    | WithItem -> group.Add e
                    | WithoutItem -> () 

            match drain () with 
            | None -> ()
            | Some g -> yield g |]          
            
        /// Group the elements in an array by a supplied function which returns the desired grouping action.
        [<CompiledName("GroupBy")>]
        let inline groupBy f source =
            let fx a b = f a
            source |> groupBy2 fx  

        /// Return the nth element of an array.
        [<CompiledName("Nth")>]
        let inline nth index (source:'t array) =
            source.[index]

        /// Return the nth element of an array as an option, if it exists, or none.
        [<CompiledName("NthOrNone")>]
        let nthOrNone index (source:'t array) =
            if index < source.Length then Some source.[index]
            else None

        /// Return the nth element of an array, if it exists, or the supplied default value.
        [<CompiledName("NthOrDefaultTo")>]
        let inline nthOrDefaultTo index defaultValue (source:'t array) =
            source |> nthOrNone index |> Option.mapOrDefaultTo id defaultValue

        /// Return the nth element of an array, if it exists, or the generic default.
        [<CompiledName("NthOrDefault")>]
        let inline nthOrDefault index (source:'t array) =
            source |> nthOrNone index |> Option.mapOrDefault id

        /// Return the nth element from the end of an array.
        [<CompiledName("NthFromEnd")>]
        let inline nthFromEnd index (source:'t array) =
            source.[source.Length - 1 - index]

        /// Return the nth element from the end of an array as an option, if it exists, or none.
        [<CompiledName("NthFromEndOrNone")>]
        let inline nthFromEndOrNone index (source:'t array) =
            let lastIndex = source.Length - 1
            if lastIndex >= 0 && index <= lastIndex then Some source.[lastIndex - index]
            else None

        /// Return the nth element from the end of an array, if it exists, or the supplied default value.
        [<CompiledName("NthFromEndOrDefaultTo")>]
        let inline nthFromEndOrDefaultTo index defaultValue (source:'t array) =
            source |> nthFromEndOrNone index |> Option.mapOrDefaultTo id defaultValue

        /// Return the nth element from the end of an array, if it exists, or the generic default.
        [<CompiledName("NthFromEndOrDefault")>]
        let inline nthFromEndOrDefault index (source:'t array) =
            source |> nthFromEndOrNone index |> Option.mapOrDefault id

        /// Skip n elements of an array, and return the rest.
        [<CompiledName("Skip")>]
        let inline skip n (source:'t array) =
            if n <= source.Length then source.[n..] else [||]

        /// Skip n elements at the end of an array, and return the rest.
        [<CompiledName("SkipLast")>]
        let inline skipLast n (source:'t array) =
            if n >= source.Length then [||] else source.[..source.Length - 1 - n]

        /// Take n elements from the beginning of an array, skip the rest.
        [<CompiledName("Take")>]
        let inline take n (source:'t array) =
            if n >= source.Length then source else source.[0..n - 1]     
        
        /// Take the last n elements from the end of an array, skip the rest.
        [<CompiledName("TakeLast")>]
        let inline takeLast n (source:'t array) =
            if n >= source.Length then source else source.[source.Length - n..source.Length - 1]

        /// Test if any element in the array matches the supplied value.
        [<CompiledName("Contains")>]
        let inline contains item source = source |> Array.exists (fun candidate -> candidate = item)
